﻿using System;
using System.Collections.Generic;
using System.Linq;


namespace DVBViMon.Utils
{
	/// <summary>
	/// Represents a logable object
	/// </summary>
	public class LogEntry
	{
		public LogEntry() {}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="timestamp">entry date and time</param>
		/// <param name="level">entry level</param>
		/// <param name="message">message text</param>
		/// <param name="details">array of details</param>
		public LogEntry (DateTime?	   timestamp,
						 MessageLevel? level,
						 string		   message,
						 string[]	   details = null)
		{
			this.Timestamp = timestamp;
			this.Level	   = level;
			this.Message   = message;

			if (details != null && details.Any())
			{
				// build detail array
				this.Details = new LogEntry [details.Length];

				for (var i = 0; i < details.Length; i++)
					this.Details [i] = new LogEntry (null, null, details [i]);
			}
		}

		#region Properties

		public DateTime?	 Timestamp	{ get; private set; }
		public MessageLevel? Level		{ get; private set; }
		public string		 Message	{ get; private set; }
		public LogEntry[]	 Details	{ get; private set; }
		
		#endregion
	}


    /// <summary>
    /// Represents access point for logging operations
    /// </summary>
    public class Logger<T> : ILogger<T> where T : LogEntry, new()
    {
        public Logger()
        {
            this.MaxEntries = -1;
            this.logEntries = new Queue<T>();
        }

        #region Interface implementation

        public void Write(string message, MessageLevel level = MessageLevel.Information, string[] details = null)
        {
            this.Write(this.CreateEntry(message, level, details));
        }

        public void Write(string message, Exception exception)
        {
            this.Write(this.CreateEntry(message, MessageLevel.Error, ExceptionMessages(exception).ToArray()));
        }

        public void Reset()
        {
            lock (this)
            {
                this.logEntries.Clear();
            }
        }

        public void Subscribe(LogUpdateEventHandler<T> handler)
        {
            if (handler == null)
                throw new ArgumentNullException("handler");

            this.Subscriber += handler;
        }

        public void Unsubscribe(LogUpdateEventHandler<T> handler)
        {
            if (handler == null)
                throw new ArgumentNullException("handler");

            this.Subscriber -= handler;
        }

        #endregion

        public static List<string> ExceptionMessages(Exception exception)
        {
            var details = new List<string>();

            if (!string.IsNullOrWhiteSpace(exception.Message))
                details.Add(exception.Message);

            // append all available inner exception messages
            while (exception.InnerException != null)
            {
                if (!string.IsNullOrWhiteSpace(exception.InnerException.Message))
                    details.Add(exception.InnerException.Message);

                exception = exception.InnerException;
            }
            return details;
        }

        #region Implementation

        /// <summary>
        /// Create a new <see cref="LogEntry"/> (base implementation)
        /// </summary>
        /// <returns>log entry instance</returns>
        protected virtual T CreateEntry(string message, MessageLevel level, string[] details)
        {
            return new LogEntry(DateTime.Now, level, message, details) as T;
        }

        protected virtual void Write(T entry)
        {
            lock (this)
            {
                this.logEntries.Enqueue(entry);

                // keep entry size constant
                while (this.MaxEntries > 0 && this.logEntries.Count > this.MaxEntries)
                    this.Subscriber(null, this.logEntries.Dequeue());

                // update subscribers
                if (this.Subscriber != null)
                    this.Subscriber(entry, null);
            }
        }

        #endregion

        #region Properties

        public IEnumerable<T> Entries { get { return this.logEntries; } }

        public int MaxEntries { get; set; }

        #endregion

        #region Events

        event LogUpdateEventHandler<T> Subscriber;

        #endregion

        #region Fields

        readonly Queue<T> logEntries;

        #endregion
	}
}
